<template>
	<view class="e-number-box">
		<view class="e-number-box__slot" @tap.stop="clickHandler('minus')" @touchstart="onTouchStart('minus')"
			@touchend.stop="_clearTimeout" v-if="props.showMinus && $slots.minus">
			<slot name="minus" />
		</view>
		<view v-else-if="props.showMinus" class="e-number-box__minus" @tap.stop="clickHandler('minus')"
			@touchstart="onTouchStart('minus')" @touchend.stop="_clearTimeout" hover-class="e-number-box__minus--hover"
			hover-stay-time="150" :class="{ 'e-number-box__minus--disabled': isDisabled('minus') }"
			:style="[buttonStyle('minus')]">
			<e-icon name="minus" :color="isDisabled('minus') ? '#c8c9cc' : '#323233'" size="15" bold
				:customStyle="props.iconStyle"></e-icon>
		</view>

		<slot name="input">
			<input :disabled="props.disabledInput || elDisabled" :cursor-spacing="getCursorSpacing"
				:class="{ 'e-number-box__input--disabled': elDisabled || props.disabledInput }" v-model="currentValue"
				class="e-number-box__input" @blur="onBlur" @focus="onFocus" @input="onInput" type="number"
				:style="[inputStyle]" />
		</slot>
		<view class="e-number-box__slot" @tap.stop="clickHandler('plus')" @touchstart="onTouchStart('plus')"
			@touchend.stop="_clearTimeout" v-if="props.showPlus && $slots.plus">
			<slot name="plus" />
		</view>
		<view v-else-if="props.showPlus" class="e-number-box__plus" @tap.stop="clickHandler('plus')"
			@touchstart="onTouchStart('plus')" @touchend.stop="_clearTimeout" hover-class="e-number-box__plus--hover"
			hover-stay-time="150" :class="{ 'e-number-box__minus--disabled': isDisabled('plus') }"
			:style="[buttonStyle('plus')]">
			<e-icon name="plus" :color="isDisabled('plus') ? '#c8c9cc' : '#323233'" size="15" bold
				:customStyle="props.iconStyle"></e-icon>
		</view>
	</view>
</template>

<script lang="ts">
	import {defineComponent} from 'vue'
	import mpMixin from "../../libs/mixin/mpMixin";
	/**
	 * numberBox 步进器
	 * @description 该组件一般用于商城购物选择物品数量的场景。
	 * @tutorial https://uviewui.com/components/numberBox.html
	 * @property {String | Number}	name			步进器标识符，在change回调返回
	 * @property {String | Number}	value			用于双向绑定的值，初始化时设置设为默认min值(最小值)  （默认 0 ）
	 * @property {String | Number}	min				最小值 （默认 1 ）
	 * @property {String | Number}	max				最大值 （默认 Number.MAX_SAFE_INTEGER ）
	 * @property {String | Number}	step			加减的步长，可为小数 （默认 1 ）
	 * @property {Boolean}			integer			是否只允许输入整数 （默认 false ）
	 * @property {Boolean}			disabled		是否禁用，包括输入框，加减按钮 （默认 false ）
	 * @property {Boolean}			disabledInput	是否禁用输入框 （默认 false ）
	 * @property {Boolean}			asyncChange		是否开启异步变更，开启后需要手动控制输入值 （默认 false ）
	 * @property {String | Number}	inputWidth		输入框宽度，单位为px （默认 35 ）
	 * @property {Boolean}			showMinus		是否显示减少按钮 （默认 true ）
	 * @property {Boolean}			showPlus		是否显示增加按钮 （默认 true ）
	 * @property {String | Number}	decimalLength	显示的小数位数
	 * @property {Boolean}			longPress		是否开启长按加减手势 （默认 true ）
	 * @property {String}			color			输入框文字和加减按钮图标的颜色 （默认 '#323233' ）
	 * @property {String | Number}	buttonSize		按钮大小，宽高等于此值，单位px，输入框高度和此值保持一致 （默认 30 ）
	 * @property {String}			bgColor			输入框和按钮的背景颜色 （默认 '#EBECEE' ）
	 * @property {String | Number}	cursorSpacing	指定光标于键盘的距离，避免键盘遮挡输入框，单位px （默认 100 ）
	 * @property {Boolean}			disablePlus		是否禁用增加按钮 （默认 false ）
	 * @property {Boolean}			disableMinus	是否禁用减少按钮 （默认 false ）
	 * @property {Object ｜ String}	iconStyle		加减按钮图标的样式
	 *
	 * @event {Function}	onFocus	输入框活动焦点
	 * @event {Function}	onBlur	输入框失去焦点
	 * @event {Function}	onInput	输入框值发生变化
	 * @event {Function}	onChange
	 * @example <e-number-box v-model="value" @change="valChange"></e-number-box>
	 */
	export default defineComponent({
		name: 'e-number-box',
		mixins: [mpMixin]
	})
</script>

<script lang="ts" setup>
	import {
		ref,
		computed,
		watch,
		onMounted,
		nextTick,
		getCurrentInstance,
		toRefs
	} from 'vue'
	import {
		numberBoxProps,
		numberBoxEmits
	} from './props';
	import {
		accAdd
	} from '../../libs/utils'
	import {
		addUnit,
		getPx
	} from '../../libs/utils/_utils'
	import type {
		StyleProperties
	} from '../../libs/tokens'
	import {
		useFormItem,
		useDisabled,
		useDefaultProps
	} from '../../libs/hooks'
	import componentsProps, {
		type DefaultProps
	} from "../../libs/config/componentsProps";

	const ctx = getCurrentInstance()
	const {
		numberBox: numberBoxDefaultProps
	} = toRefs(componentsProps);

	const _props = defineProps(numberBoxProps)

	const props = useDefaultProps < DefaultProps['numberBox'] > (_props, numberBoxDefaultProps);
	const emit = defineEmits(numberBoxEmits)


	const {
		form,
		formItem
	} = useFormItem()

	const currentValue = ref('')
	let longPressTimer: number | undefined = undefined;
	let operationType: "minus" | "plus" | "" = ''

	watch(() =>[props.value.integer, props.value.decimalLength, props.value.min, props.value.max], () => {
		check()
	})

	watch(() => props.value.modelValue, (n) => {
		// 尝试调用e-form的验证方法，进行一定延迟，否则微信小程序更新可能会不及时
		nextTick(() => {
			// 如果number-box是在form组件内，调用form组件的validateField
			if (formItem && form) {
				form.validateField([formItem.props.prop], () => {}, 'change')
			}
		})
		if (n !== currentValue.value) {
			currentValue.value = format(n as string)

		}
	})
	const getCursorSpacing = computed(() => {
		// 判断传入的单位，如果为px单位，需要转成px
		return getPx(props.value.cursorSpacing as number)
	})
	// 输入框的样式
	const inputStyle = computed(() => {
		const style = {
			color: props.value.color,
			backgroundColor: props.value.bgColor,
			height: addUnit(props.value.buttonSize),
			width: addUnit(props.value.inputWidth)
		}
		return style
	})

	const elDisabled = useDisabled()

	// 按钮的样式
	const buttonStyle = (type: "minus" | "plus") => {
		const style: StyleProperties = {
			backgroundColor: props.value.bgColor,
			height: addUnit(props.value.buttonSize),
			color: props.value.color
		}
		if (isDisabled(type)) {
			style.backgroundColor = '#f7f8fa'
		}
		return style
	}
	const isDisabled = (type: "minus" | "plus" | "") => {
		if (type === 'plus') {
			// 在点击增加按钮情况下，判断整体的disabled，是否单独禁用增加按钮，以及当前值是否大于最大的允许值
			return (
				elDisabled.value ||
				props.value.disablePlus ||
				Number(currentValue.value) >= (props.value.max as number)
			)
		}
		// 点击减少按钮同理
		return (
			elDisabled.value ||
			props.value.disableMinus ||
			Number(currentValue.value) <= (props.value.min as number)
		)
	}

	const init = () => {
		currentValue.value = format(props.value.modelValue as number)
	}
	// 格式化整理数据，限制范围
	const format = (value: number | string) => {
		value = filter(value)
		// 如果为空字符串，那么设置为0，同时将值转为Number类型
		value = value === '' ? 0 : +value
		// 对比最大最小值，取在min和max之间的值
		value = Math.max(Math.min(props.value.max as number, value), props.value.min as number)
		// 如果设定了最大的小数位数，使用toFixed去进行格式化
		if (props.value.decimalLength !== null) {
			value = value.toFixed(props.value.decimalLength as number)
		}
		return 	`${value}`
	}
	// 过滤非法的字符
	const filter = (value: string | number) => {
		// 只允许0-9之间的数字，"."为小数点，"-"为负数时候使用
		value = String(value).replace(/[^0-9.-]/g, '')
		// 如果只允许输入整数，则过滤掉小数点后的部分
		if (props.value.integer && value.indexOf('.') !== -1) {
			value = value.split('.')[0]
		}
		return value;
	}
	const check = () => {
		// 格式化了之后，如果前后的值不相等，那么设置为格式化后的值
		const val = format(currentValue.value);
		if (val !== currentValue.value) {
			currentValue.value = val
		}
	}
	// 输入框活动焦点
	const onFocus = (event: {
		detail: {
			value: number
		}
	}) => {
		emit('focus', {
			...event.detail,
			name: props.value.name,
		})
	}
	// 输入框失去焦点
	const onBlur = (event: {
		detail: {
			value: number
		}
	}) => {
		// 对输入值进行格式化
		const value = format(event.detail.value)
		// 发出blur事件
		emit('blur', {
			value,
			name: props.value.name,
		})
		// 尝试调用e-form的验证方法，进行一定延迟，否则微信小程序更新可能会不及时
		nextTick(() => {
			// 如果number-box是在form组件内，调用form组件的validateField
			if (formItem && form) {
				form.validateField([formItem.props.prop], () => {}, 'blur')
			}
		})

	}
	// 输入框值发生变化
	const onInput = (e: {
		detail: {
			value: number
		}
	}) => {
		const {
			value = ''
		} = e.detail || {}
		// 为空返回
		if (value === '') return
		let formatted = filter(value)
		// 最大允许的小数长度
		if (props.value.decimalLength !== null && formatted.indexOf('.') !== -1) {
			const pair = formatted.split('.');
			formatted = `${pair[0]}.${pair[1].slice(0, props.value.decimalLength as number)}`
		}
		emitChange(format(formatted));
	}
	// 发出change事件
	const emitChange = (value: string) => {
		// 如果开启了异步变更值，则不修改内部的值，需要用户手动在外部通过v-model变更
		if (!props.value.asyncChange) {
			nextTick(() => {
				emit('update:modelValue', value)
				currentValue.value = value
				ctx?.proxy?.$forceUpdate()
			})
		}
		emit('change', {
			value,
			name: props.value.name,
		});
	}
	const onChange = () => {
		if (isDisabled(operationType)&& operationType !== '') {
			return emit('overlimit', operationType)
		}
		const diff = operationType === 'minus' ? -(props.value.step as number) : +(props.value.step as number)
		const value = format(accAdd(+currentValue.value, diff))
		emitChange(value)
		emit(operationType as 'minus')
	}
	// 点击加减按钮
	const clickHandler = (type: "minus" | "plus") => {
		operationType = type
		onChange()
	}
	const longPressStep = () => {
		// 每隔一段时间，重新调用longPressStep方法，实现长按加减
		_clearTimeout()
		longPressTimer = setTimeout(() => {
			onChange()
			longPressStep()
		}, 250);
	}
	const onTouchStart = (type: "minus" | "plus") => {
		if (!props.value.longPress) return
		_clearTimeout()
		operationType = type
		// 一定时间后，默认达到长按状态
		longPressTimer = setTimeout(() => {
			onChange()
			longPressStep()
		}, 600)
	}
	// 触摸结束，清除定时器，停止长按加减
	const onTouchEnd = () => {
		if (!props.value.longPress) return
		_clearTimeout()
	}
	// 清除定时器
	const _clearTimeout = () => {
		clearTimeout(longPressTimer)
		longPressTimer = undefined
	}

	onMounted(() => {
		init()
	})
</script>

<style lang="scss" scoped>
	@import '../../libs/theme/number-box.scss';
</style>
